/**
 * 不要在这里引用 engiGis, 保持这个对象的单向依赖
 */

import { Observable } from 'rxjs';
import { IndexDbManager } from './index.db';
import { ObjModelService } from './service';
import { simpleHashMixed } from './utils';
import { DatabaseManager } from './databaseManager';
import {
  BizUnitType,
  ModelBaseInfo,
  ModelInfoFrom,
  ModelObjectTypeInfo,
  ModelObjType,
  ObjectGisModelInfo,
  ObjectGisModelInfoResponse,
} from '../shared';
import type * as GeoJSON from 'geojson';

/**
 * 计算缩放级别显示逻辑
 * nmap只会返回值为1的项
 * zoom会返回缩放比例
 * @param nmap
 * @param zoom
 * @returns
 */
function calcZoom(
  nmap: Record<string, 1> = {},
  zoom: Record<string, number> = {},
) {
  const nmapKeys = Object.keys(nmap)
    .map((k) => Number(k.slice(1)))
    .sort((a, b) => a - b);
  const minNmap = nmapKeys.length > 0 ? nmapKeys[0] : 24;
  const maxNmap = nmapKeys.length > 0 ? nmapKeys[nmapKeys.length - 1] : 0;

  const zoomEntries = Object.entries(zoom)
    .map((z) => {
      const zoomKey = Number(z[0].slice(1)); // 去掉前缀 'z'
      return [zoomKey, z[1]];
    })
    .sort((a, b) => a[0] - b[0]);
  const fmtZoom: Record<string, number> = {};
  const tempMinZoom = zoomEntries.find((ze) => ze[1] !== 0); //  第一个不为 0 的 zoom值
  const minZoom = tempMinZoom
    ? tempMinZoom[0]
    : zoomEntries.length
      ? zoomEntries[zoomEntries.length - 1][0] + 1
      : 25;
  for (let i = minZoom; i <= 24; i++) {
    if (zoom[`z${i}`] !== undefined) {
      const t = zoom[`z${i}`];
      fmtZoom[`z${i}`] = t > 1 ? t / 100 : t; // 如果大于1，转换为百分比
    } else {
      fmtZoom[`z${i}`] = fmtZoom[`z${i - 1}`] || 1; // 如果没有配置，使用默认值
    }
  }

  return {
    minN: minNmap,
    maxN: maxNmap,
    ...fmtZoom,
    minZ: minZoom,
  };
}

/**
 * 对象模型管理器， 用于管理模型对象的地理信息的加载、更新和查询等操作
 */
export class ObjectModelManager {
  private db!: IndexDbManager;
  private modelObjectTypeInfoList: ModelObjectTypeInfo[] = [];
  private svc: ObjModelService;
  private databaseManager = new DatabaseManager();
  private dbName: string = '';
  // 项目基础信息
  private baseInfo: {
    projectName: string;
    modelType: string;
    userGroupIds: number[];
    userId: number;
    modelId: string;
    gisSettingUpdateTime: string;
  } = {
    projectName: '',
    modelType: '',
    userGroupIds: [],
    userId: NaN,
    modelId: '',
    gisSettingUpdateTime: '',
  };

  constructor(httpService?: any) {
    this.svc = new ObjModelService(httpService);
  }

  /**
   * 获取模型对象的类型信息
   * 来源： /digital-service/modelObjInfo/modelObjInfo
   * @returns 模型对象类型名称列表
   */
  get modelObjTypeList(): ModelObjType[] {
    return this.modelObjectTypeInfoList.map((item) => item.key);
  }

  /**
   * 加载模型对象类型信息
   * @returns 模型对象类型信息
   */
  private async loadModelObjectInfo(): Promise<ModelObjectTypeInfo[]> {
    const response = await this.svc.getModelObjInfo();
    const data = response.data;
    return data;
  }

  /**
   * 从indexedDb 或 服务端 加载模型GIS信息
   * @param type 模型对象类型
   */
  private async loadModelObjInfoByType(
    type: ModelObjType,
  ): Promise<ObjectGisModelInfoResponse> {
    const db = this.db;
    const data = await db.getAll<ObjectGisModelInfo>(type);
    const modelObjInfo = this.modelObjectTypeInfoList.find(
      (item) => item.key === type,
    );
    if (!modelObjInfo) {
      throw new Error(`Model object type info not found for type: ${type}`);
    }
    // 异步更新至元信息表 （记录日志）
    setTimeout(() => {
      this.databaseManager.updateDatabase(this.dbName);
    }, 0);
    if (data?.length) {
      // 从缓存获取数据后，不再按单个类型计算边界信息
      // 边界计算将在全局统一处理
      return {
        type,
        data,
        from: 'cache',
        modelObjInfo,
      };
    } else {
      const res = await this.svc.getModelGisInfo(type);
      console.info(`Loading GIS info for model type: ${type}, response:`);
      if (res.code === 200) {
        let data;
        try {
          const parsed = JSON.parse(res.data as string);
          data = parsed.map((item: any) => {
            if (!item.properties) {
              item.properties = {};
            }
            item.properties.type = type; // 确保每个特征都有type属性
            // item.properties = calcZoom(item.properties.nmap, item.properties.zoom);
            const zooms = calcZoom(item.properties.nmap, item.properties.zoom);
            item.properties = {
              ...item.properties,
              ...zooms,
            };
            return item;
          });
          console.info(`Parsed GIS info for model type ${type}:`, data);

          // 网络请求获取数据后，不再按单个类型计算边界信息
          // 边界计算将在全局统一处理
        } catch (err) {
          console.error(
            `Failed to parse GIS info for model type ${type}:`,
            err,
          );
          throw new Error(`Invalid GIS info format for model type ${type}`);
        }
        console.info(`Adding GIS info for model type: ${type}`);
        // 异步添加到数据库，这里不需要阻塞主线程
        setTimeout(() => {
          // 添加到业务库
          db.put(type, data);
        }, 0);
        const modelObjInfo = this.modelObjectTypeInfoList.find(
          (item) => item.key === type,
        )!;
        return {
          type,
          data,
          from: 'network',
          modelObjInfo,
        };
      } else {
        throw new Error(
          `Failed to get GIS info for model type ${type}: ${res.msg}`,
        );
      }
    }
  }

  /**
   * 获取模型对象地理信息源，以Observable方式返回
   * @returns 返回一个Observable，每次收到更新时发出更新后的GeoJSON数据源
   */
  getModelObjectGisInfoSourceWithObservable(): Observable<{
    type: ModelObjType;
    source: any;
    // source: mapboxgl.GeoJSONSourceSpecification;
  }> {
    console.info('获取模型对象地理信息源 with Observable');

    return new Observable((observer) => {
      // 保存所有接收到的特征
      const allFeatures: ObjectGisModelInfo[] = [];
      // 已经处理的类型数量
      let processedCount = 0;
      // 要处理的总类型数量
      const totalTypesCount = this.modelObjTypeList.length;

      // 订阅GIS信息Observable
      const subscription = this.getModelObjectGisInfoWithObservable().subscribe(
        {
          next: (info: ObjectGisModelInfoResponse) => {
            // 将特征添加到集合中
            allFeatures.push(...info.data);
            processedCount++;

            // 创建GeoJSON数据源，将ObjectGisModelInfo转换为正确的Feature类型
            // const source: mapboxgl.GeoJSONSourceSpecification = {
            const source: any = {
              type: 'geojson',
              data: {
                type: 'FeatureCollection',
                features: allFeatures as unknown as GeoJSON.Feature<
                  GeoJSON.Geometry,
                  GeoJSON.GeoJsonProperties
                >[],
              },
            };

            // 每次收到更新都发出当前的数据源
            observer.next({
              type: info.type,
              source,
            });

            // 如果所有类型都处理完了，标记为完成
            if (processedCount >= totalTypesCount) {
              observer.complete();
            }
          },
          error: (err) => {
            // 发生错误时，发送错误并结束Observable
            observer.error(err);
          },
          complete: () => {
            // 确保流程完成
            if (processedCount < totalTypesCount) {
              observer.complete();
            }
          },
        },
      );

      // 返回清理函数
      return () => {
        subscription.unsubscribe();
        console.info('GeoJSON数据源Observable已被取消订阅');
      };
    });
  }

  getModelObjectGisInfoWithObservable(): Observable<ObjectGisModelInfoResponse> {
    if (!this.db) {
      throw new Error('请先通过 init() 方法初始化 ObjectModelManager');
    }
    const modelObjTypes = this.modelObjTypeList;

    // 从模型类型列表创建Observable，并对每个类型调用loadModelObjInfoByType
    return new Observable<ObjectGisModelInfoResponse>((observer) => {
      // 为每种类型创建一个Promise
      const promises = modelObjTypes.map(async (type) => {
        try {
          // 加载该类型的GIS信息
          const info = await this.loadModelObjInfoByType(type);
          // 将结果发送给Observer
          observer.next(info);
          return info; // 返回结果以便Promise.all可以等待所有完成
        } catch (error) {
          console.error(`从类型 ${type} 加载GIS信息时出错:`, error);
          // 发送空结果，避免流中断
          observer.next({
            type,
            data: [],
            from: 'error' as ModelInfoFrom,
            modelObjInfo: this.modelObjectTypeInfoList.find(
              (item) => item.key === type,
            )!,
          });
          return null; // 返回null，让Promise.all继续执行
        }
      });

      // 当所有Promise都完成后，完成Observable流
      Promise.all(promises)
        .then(() => observer.complete())
        .catch((err) => observer.error(err));

      // 返回清理函数
      return () => {
        // 如果需要清理资源，可以在这里处理
        console.info('Observable已被取消订阅');
      };
    });
  }

  /**
   * 加载模型对象的地理信息
   * @param modelObjTypes 模型对象类型列表
   */
  async getModelObjectGisInfo() {
    if (!this.db) {
      throw new Error('请先通过 init() 方法初始化 ObjectModelManager');
    }
    const modelObjTypes = this.modelObjTypeList;
    const objGisInfoGroupList = await Promise.all(
      modelObjTypes.map(this.loadModelObjInfoByType.bind(this)),
    );
    return objGisInfoGroupList;
  }

  /**
   *
   * @returns 模型对象的地理信息源
   */
  async getModelObjectGisInfoSource() {
    console.info('获取模型对象地理信息源');
    const info = await this.getModelObjectGisInfo();

    // 获取所有特征
    const allFeatures: any = info.reduce((acc: any, item: any) => {
      acc.push(...item.data);
      return acc;
    }, [] as ObjectGisModelInfo[]);

    // 计算并缓存全局边界
    this.calculateAndStoreGlobalBounds(allFeatures);

    const source = {
      type: 'geojson',
      data: {
        type: 'FeatureCollection',
        features: allFeatures,
      },
    };
    return source;
  }

  /**
   * 计算并存储全局边界信息
   * @param allFeatures 所有特征
   */
  private async calculateAndStoreGlobalBounds(
    allFeatures: ObjectGisModelInfo[],
  ): Promise<void> {
    try {
      // 检查是否已经有缓存的边界
      const cachedBounds = await this.getModelGlobalBounds();

      // 如果已有缓存，则不重新计算
      if (cachedBounds) {
        console.info('已有缓存的全局边界信息，跳过计算');
        return;
      }

      console.info('计算全局边界信息...');
      const bounds = this.calculateAllModelBounds(allFeatures);

      if (bounds) {
        // 存储计算结果到缓存
        await this.storeBaseInfo('globalBounds', bounds);
        console.info('已计算并存储全局边界信息');
      } else {
        console.warn('计算全局边界失败，无法缓存');
      }
    } catch (error) {
      console.error('计算并存储全局边界信息时出错:', error);
    }
  }

  /**
   * 获取指定类型的模型对象地理信息
   * @param modelObjType 模型对象类型
   */
  async getModelObjectGisInfoByType(modelObjType: ModelObjType) {
    if (!this.db) {
      throw new Error('请先通过 init() 方法初始化 ObjectModelManager');
    }
    return this.loadModelObjInfoByType(modelObjType);
  }

  private async initDb(modelNames: ModelObjType[]): Promise<IndexDbManager> {
    if (!this.databaseManager.loaded) {
      await this.databaseManager.init();
    }
    const name = this.getDbName();
    const db = new IndexDbManager({
      name,
      stores: [
        // 模型对象存储
        ...modelNames.map((key) => ({
          name: key,
          keyPath: 'id',
        })),
        // 模型计算值缓存表，用于存储需要大量计算的内容的缓存, 基础信息表
        {
          name: 'baseInfo',
          keyPath: 'id',
        },
      ],
      version: 2, // 更新版本号以触发数据库升级
    });
    const connectResult = await db.connect();
    await this.databaseManager.addIfNotExists(name);
    console.info('IndexDB connect:', connectResult);
    this.db = db;
    this.storeBaseInfo('projectName', this.baseInfo.projectName);
    this.storeBaseInfo('modelType', this.baseInfo.modelType);
    this.storeBaseInfo('userGroupIds', this.baseInfo.userGroupIds);
    this.storeBaseInfo('userId', this.baseInfo.userId);
    this.storeBaseInfo('modelId', this.baseInfo.modelId);
    this.storeBaseInfo(
      'gisSettingUpdateTime',
      this.baseInfo.gisSettingUpdateTime,
    );
    return db;
  }

  async init(
    projectName: string,
    modelType: string,
    userGroupIds: number[],
    userId: number,
    modelId?: string,
    updateTime?: string,
  ): Promise<ModelObjectTypeInfo[]> {
    if (!projectName) {
      throw new Error('项目名称不能为空');
    }
    this.baseInfo.projectName = projectName;
    if (!modelType) {
      throw new Error('模型类型不能为空');
    }
    this.baseInfo.modelType = modelType;
    this.baseInfo.userGroupIds = userGroupIds || [];
    this.baseInfo.userId = userId || NaN;
    const [modelObjInfo] = await Promise.all([
      // 初始化模型对象类型信息
      this.loadModelObjectInfo().then((data) => {
        this.modelObjectTypeInfoList = data; // 根据sort， 决定绘制顺序。 sort大 -> 先画 -> 在底层
        console.info(data, 'modelobjinfo');
        return data;
      }),
      // 初始化模型id
      modelId ? Promise.resolve(modelId) : this.initModelId(),
      // 初始化GIS配置的最新更新时间
      updateTime
        ? Promise.resolve(updateTime)
        : this.initLatestGisSettingUpdateTime(),
    ]);
    // 初始化 indexdb
    // await this.initDb(modelNames);
    const modelObjTypeList = modelObjInfo.map((item) => item.key)
    await this.initDb(modelObjTypeList);
    return modelObjInfo;
  }

  async updateModelObjectGisInfo(
    type: BizUnitType,
    data: {
      id: string;
      properties: Record<string, any>;
    }[],
  ) {
    if (!this.db) {
      throw new Error('请先通过 init() 方法初始化 ObjectModelManager');
    }
    return this.db.updateProperties(type, data).then((res) => {
      return res;
    });
  }

  async getObjectById(
    id: string,
    type?: ModelObjType,
  ): Promise<ObjectGisModelInfo | null> {
    if (!this.db) {
      throw new Error('请先通过 init() 方法初始化 ObjectModelManager');
    }
    if (!type) {
      // 如果没有指定类型，尝试从所有类型中查找
      for (const modelType of this.modelObjTypeList) {
        const data = await this.db.get<ObjectGisModelInfo>(modelType, id);
        if (data) {
          return data;
        }
      }
      return null; // 如果没有找到，返回null
    } else {
      // 如果指定了类型，直接查询该类型
      const data = await this.db.get<ObjectGisModelInfo>(type, id);
      return data || null; // 如果没有找到，返回null
    }
  }

  /**
   * 计算模型数据的边界信息
   * @param features 需要计算边界的GeoJSON特征集合
   * @private
   */
  private calculateAllModelBounds(
    features: ObjectGisModelInfo[],
  ): { minLng: number; minLat: number; maxLng: number; maxLat: number } | null {
    try {
      if (!features || features.length === 0) {
        return null;
      }

      let minLng = Infinity;
      let minLat = Infinity;
      let maxLng = -Infinity;
      let maxLat = -Infinity;

      // 遍历所有特征以计算边界
      for (const feature of features) {
        const geometry = (feature as GeoJSON.Feature).geometry;

        if (!geometry) continue;

        const coordinates = this.extractCoordinatesFromGeometry(geometry);

        if (!coordinates || coordinates.length === 0) continue;

        // 更新边界
        for (const [lng, lat] of coordinates) {
          minLng = Math.min(minLng, lng);
          minLat = Math.min(minLat, lat);
          maxLng = Math.max(maxLng, lng);
          maxLat = Math.max(maxLat, lat);
        }
      }

      // 检查是否找到了任何坐标
      if (
        minLng === Infinity ||
        minLat === Infinity ||
        maxLng === -Infinity ||
        maxLat === -Infinity
      ) {
        return null;
      }

      return { minLng, minLat, maxLng, maxLat };
    } catch (error) {
      console.error('计算模型边界时出错:', error);
      return null;
    }
  }

  /**
   * 从地理几何体中提取所有坐标点
   * @param geometry GeoJSON几何体
   * @returns 坐标点数组
   * @private
   */
  private extractCoordinatesFromGeometry(
    geometry: GeoJSON.Geometry,
  ): [number, number][] {
    const coords: [number, number][] = [];

    try {
      switch (geometry.type) {
        case 'Point':
          coords.push([geometry.coordinates[0], geometry.coordinates[1]]);
          break;

        case 'LineString':
          for (const coord of geometry.coordinates) {
            coords.push([coord[0], coord[1]]);
          }
          break;

        case 'Polygon':
          for (const ring of geometry.coordinates) {
            for (const coord of ring) {
              coords.push([coord[0], coord[1]]);
            }
          }
          break;

        case 'MultiPoint':
          for (const point of geometry.coordinates) {
            coords.push([point[0], point[1]]);
          }
          break;

        case 'MultiLineString':
          for (const line of geometry.coordinates) {
            for (const coord of line) {
              coords.push([coord[0], coord[1]]);
            }
          }
          break;

        case 'MultiPolygon':
          for (const polygon of geometry.coordinates) {
            for (const ring of polygon) {
              for (const coord of ring) {
                coords.push([coord[0], coord[1]]);
              }
            }
          }
          break;

        case 'GeometryCollection':
          for (const geom of geometry.geometries) {
            coords.push(...this.extractCoordinatesFromGeometry(geom));
          }
          break;
      }

      return coords;
    } catch (error) {
      console.error('从几何体中提取坐标时出错:', error);
      return [];
    }
  }

  /**
   * 向计算值缓存表中存储数据
   * @param id 缓存ID
   * @param value 缓存值
   * @private
   */
  private async storeBaseInfo<T>(id: string, value: T): Promise<void> {
    if (!this.db) {
      console.error('数据库未初始化，无法存储缓存数据');
      return;
    }

    try {
      const cacheData = {
        id,
        value,
      };

      await this.db.put('baseInfo', cacheData);
      console.info(`已存储缓存数据: ${id}`);
    } catch (error) {
      console.error(`存储缓存数据 ${id} 时出错:`, error);
    }
  }

  /**
   * 获取模型的基本信息， 从 indexeddb 中的 `baseInfo` 表获取， 可用的key： 模型边界信息(globalBounds)，gis配置更新时间(gisSettingUpdateTime)， modelId，modelType, projectName, userGroupIds, userId
   * @param key 需要获取的key
   * @returns 缓存值或null
   */
  async getBaseInfo<T>(key: string): Promise<T | null> {
    if (!this.db) {
      throw new Error('请先通过 init() 方法初始化 ObjectModelManager');
    }

    try {
      const cache = await this.db.get<ModelBaseInfo>('baseInfo', key);
      return cache ? (cache.value as T) : null;
    } catch (error) {
      console.error(`获取缓存数据 ${key} 时出错:`, error);
      return null;
    }
  }

  /**
   * 获取模型数据的全局边界
   * @returns 边界信息或null
   */
  async getModelGlobalBounds(): Promise<{
    minLng: number;
    minLat: number;
    maxLng: number;
    maxLat: number;
  } | null> {
    return this.getBaseInfo<{
      minLng: number;
      minLat: number;
      maxLng: number;
      maxLat: number;
    }>('globalBounds');
  }

  /**
   * 获取模型id
   */
  async initModelId(): Promise<string> {
    const modelIdRes = await this.svc.getModelId();
    if (modelIdRes.code === 200 && modelIdRes.data?.modelIds) {
      this.baseInfo.modelId = modelIdRes.data.modelIds;
      return this.baseInfo.modelId;
    } else {
      throw new Error(`获取模型ID失败: ${modelIdRes.msg}`);
    }
  }

  /**
   * 获取gis配置的最新更新时间
   */
  async initLatestGisSettingUpdateTime(): Promise<string> {
    const updateTimeRes = await this.svc.getLatestGisSettingUpdateTime();
    if (updateTimeRes.code === 200 && updateTimeRes.data?.createTime) {
      this.baseInfo.gisSettingUpdateTime = updateTimeRes.data.createTime;
      return this.baseInfo.gisSettingUpdateTime;
    } else {
      throw new Error(`获取GIS设置最新更新时间失败: ${updateTimeRes.msg}`);
    }
  }

  private getDbName(): string {
    const {
      projectName,
      modelType,
      userGroupIds,
      userId,
      modelId,
      gisSettingUpdateTime,
    } = this.baseInfo;

    // 用户组id
    const groupIds = userGroupIds.sort((a, b) => a - b);
    const groupSign = groupIds.length > 0 ? groupIds.join(',') : '';
    const userSign = groupSign || userId.toString() || '_super_';
    const temp = simpleHashMixed([userSign, modelId, gisSettingUpdateTime]);
    const dbName = `${projectName}${modelType}${temp}`;
    this.dbName = dbName;
    return dbName;
  }

  // --- 模型本体更新和清除逻辑 ---

  /**
   * 模型更新检查
   * 1. 当 baseInfo 发生变化时，重新拉模型
   * 2. 当 库数量超过 5， 清除最 modelId 更新时间最旧的库
   */
  async checkModelUpdate() {
    if (!this.db) {
      throw new Error('请先通过 init() 方法初始化 ObjectModelManager');
    }
    await Promise.all([
      this.initModelId(), // 获取最新的模型ID
      this.initLatestGisSettingUpdateTime(), // 获取最新的GIS设置更新时间
    ]);
    const dbName = this.getDbName(); // 获取当前数据库名称

    const dbExist = await this.databaseManager.exists(dbName);
    if (!dbExist) {
      const modelObjTypeInfo = await this.init(
        this.baseInfo.projectName,
        this.baseInfo.modelType,
        this.baseInfo.userGroupIds,
        this.baseInfo.userId,
      );
      console.log('模型对象类型信息已初始化:', modelObjTypeInfo);
      // todo bus emit
    }
    // 检查数据库数量是否超过5
    const databases = await this.databaseManager.getAll();
    if (databases.length > 5) {
      // 按更新时间排序，删除最旧的数据库
      databases.sort(
        (a, b) =>
          new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(),
      );
      const oldestDb = databases[0];
      await Promise.all([
        this.databaseManager.delete(oldestDb.name),
        IndexDbManager.deleteDatabase(oldestDb.name),
      ]).catch((err) => {
        console.error(`删除最旧的数据库 ${oldestDb.name} 时出错:`, err);
      });
      console.info(`已删除最旧的数据库: ${oldestDb.name}`);
    }

    return true;
  }

  // --- 自定义对象的GIS信息更新和查询 ---
  // updateCustomObjectGisInfo(unitType: BizUnitType, update: { id: string, properties: Record<string, any> }[]) {
  //   this.customObjData.updateProperties(unitType, update)
  // }
}
